package br.com.vti.leilao.model.dao;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;
import org.apache.log4j.Logger;

/**
 * Classe utilizada na listagem por exemplo.
 * @author Ednardo Rubens
 */
@SuppressWarnings("PublicInnerClass")
public class Example {

    public transient static final Logger logger = Logger.getLogger(Example.class);

    public enum MatchMode {

        END(0),
        START(1),
        EXACT(2),
        ANYWHERE(3);
        public final int valor;

        public int getValor() {
            return this.valor;
        }

        private MatchMode(int valor) {
            this.valor = valor;
        }
    }

    public enum PropertySelector {

        ALL(0),
        NOT_NULL(1),
        NOT_NULL_OR_ZERO(2);
        public final int valor;

        public int getValor() {
            return this.valor;
        }

        private PropertySelector(int valor) {
            this.valor = valor;
        }
    }
    
    private Object entity;
    private MatchMode matchMode;
    private PropertySelector selector;
    private Boolean isIgnoreCaseEnabled;
    private List<String> excludedProperties;

    public Example(Object entity) {
        this.entity = entity;
        this.selector = PropertySelector.ALL;
        this.matchMode = MatchMode.ANYWHERE;
        this.isIgnoreCaseEnabled = Boolean.FALSE;
        this.excludedProperties = new ArrayList<String>();
    }

    public Example excludeZeroes() {
        this.selector = PropertySelector.NOT_NULL_OR_ZERO;
        return this;
    }

    public Example excludeNone() {
        this.selector = PropertySelector.NOT_NULL;
        return this;
    }

    public Example enableLike(MatchMode matchMode) {
        this.matchMode = matchMode;
        return this;
    }

    public Example enableLike() {
        this.matchMode = MatchMode.ANYWHERE;
        return this;
    }

    public Example ignoreCase() {
        this.isIgnoreCaseEnabled = Boolean.TRUE;
        return this;
    }

    public Example excludeProperty(String name) {
        this.excludedProperties.add(name);
        return this;
    }

    protected boolean isString(Class<?> classe) {
        return String.class.isAssignableFrom(classe);
    }

    protected boolean isCollection(Class<?> classe) {
        return Collection.class.isAssignableFrom(classe);
    }

    protected boolean isPrimitiveNumber(Class<?> classe) {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        classes.add(int.class);
        classes.add(byte.class);
        classes.add(long.class);
        classes.add(short.class);
        classes.add(float.class);
        classes.add(double.class);
        return classes.contains(classe);
    }

    protected boolean isNumber(Class<?> classe) {
        return Number.class.isAssignableFrom(classe);
    }

    protected boolean isAnnotationInProperty(Object objeto, Field field, Class<? extends Annotation> classAnnotation) {
        String name = field.getName();
        boolean retorno = field.isAnnotationPresent(classAnnotation);
        if (!retorno && objeto != null && name != null && !name.isEmpty()) {
            try {
                String nomeMetodo = "get" + name.substring(0, 1).toUpperCase();
                if (name.length() > 1) {
                    nomeMetodo += name.substring(1);
                }
                Class<?> classe = objeto.getClass();
                Method methodGet = classe.getMethod(nomeMetodo, new Class<?>[]{});
                retorno = methodGet.isAnnotationPresent(classAnnotation);
            } catch (Exception e) {
                logger.error(e);
            }
        }
        return retorno;
    }

    protected boolean isPrimaryKeyProperty(Object objeto, Field field) {
        return isAnnotationInProperty(objeto, field, Id.class);
    }

    protected boolean isTransientProperty(Object objeto, Field field) {
        return isAnnotationInProperty(objeto, field, Transient.class);
    }

    protected boolean isEmpty(Object objeto) {
        boolean retorno = false;
        if (objeto != null) {
            Class<?> type = objeto.getClass();
            if (isString(type)) {
                String aux = (String) objeto;
                aux = aux.trim();
                if (aux.isEmpty()) {
                    retorno = true;
                }
            } else if (isCollection(type)) {
                Collection<?> c = (Collection) objeto;
                if (c.isEmpty()) {
                    retorno = true;
                }
            }
        }
        return retorno;
    }

    protected boolean isZero(Object objeto) {
        boolean retorno = false;
        if (objeto != null) {
            Number number = null;
            Class<?> type = objeto.getClass();
            if (isNumber(type)) {
                number = (Number) objeto;
            } else if (isPrimitiveNumber(type)) {
                String value = String.valueOf(objeto);
                if (value != null && !value.isEmpty() && !value.equals("null")) {
                    number = new Double(value);
                }
            }
            if (number != null && number.doubleValue() == 0.0d) {
                retorno = true;
            }
        }
        return retorno;
    }

    protected boolean excluirProperty(Object objeto, Field field) throws IllegalArgumentException, IllegalAccessException {
        boolean excluir = false;
        if (objeto != null && field != null) {
            String name = field.getName();
            if ("serialVersionUID".equals(name)
                    || excludedProperties.contains(name)
                    || isTransientProperty(objeto, field)) {
                excluir = true;
            } else {
                Object value = field.get(objeto);
                if (selector == PropertySelector.NOT_NULL_OR_ZERO) {
                    if (value == null || isEmpty(value) || isZero(value)) {
                        excluir = true;
                    }
                } else if (selector == PropertySelector.NOT_NULL) {
                    if (value == null || isEmpty(value)) {
                        excluir = true;
                    }
                } else if (selector == PropertySelector.ALL) {
                    if (isPrimaryKeyProperty(objeto, field)) {
                        excluir = true;
                    }
                }
            }
        }
        return excluir;
    }

    protected List<String> getFilter(Object objeto, String nameProperty) {
        List<String> filtros = new ArrayList<String>();
        if (objeto != null) {
            Class<?> type = objeto.getClass();
            Field[] fields = type.getDeclaredFields();
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    Class<?> typeJoin = field.getType();
                    String nameFilter = field.getName();
                    String keyValue = field.getName();
                    String nomeEntidade = entity.getClass().getSimpleName();
                    if (!excluirProperty(objeto, field)) {
                        if (nameProperty != null && !nameProperty.isEmpty()
                                && !nameProperty.equalsIgnoreCase(nomeEntidade)) {
                            nameFilter = nameProperty + "." + nameFilter;
                            keyValue = nameFilter.replaceAll("[.]", "_");
                        }
                        if (isString(typeJoin) && matchMode != MatchMode.EXACT) {
                            if (isIgnoreCaseEnabled) {
                                filtros.add("UPPER(" + nameFilter + ") LIKE UPPER(:" + keyValue + ")");
                            } else {
                                filtros.add(nameFilter + " LIKE :" + keyValue);
                            }
                        } else if (typeJoin.isAnnotationPresent(Entity.class)) {
                            Object valueFilter = field.get(objeto);
                            filtros.addAll(getFilter(valueFilter, nameFilter));
                        } else if (!List.class.isAssignableFrom(typeJoin)) {
                            filtros.add(nameFilter + " = :" + keyValue);
                        }
                        if (isPrimaryKeyProperty(objeto, field)) {
                            break;
                        }
                    }
                } catch (Exception e) {
                    logger.error(e);
                }
            }
        }
        return filtros;
    }
    
    public String toHqlString() {
        String hql = null;
        if (entity != null) {
            String name = entity.getClass().getSimpleName();
            hql = "FROM " + name;
            List<String> filtros = getFilter(entity, name.toLowerCase());
            for (int i = 0; i < filtros.size(); i++) {
                hql += ((i == 0) ? " WHERE " : "\tAND ") + filtros.get(i) + "\n";
            }
        }
        return hql;
    }

    protected Map<String, Object> getFilterValues(Object objeto, String nameProperty) throws SecurityException {
        Map<String, Object> propertyValues = new HashMap<String, Object>();
        if (objeto != null) {
            Class<?> type = objeto.getClass();
            Field[] fields = type.getDeclaredFields();
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    Class<?> typeJoin = field.getType();
                    String nameFilter = field.getName();
                    String keyValue = field.getName();
                    String nomeEntidade = entity.getClass().getSimpleName();
                    Object value = field.get(objeto);
                    if (!excluirProperty(objeto, field)) {
                        if (nameProperty != null && !nameProperty.isEmpty()
                                && !nameProperty.equalsIgnoreCase(nomeEntidade)) {
                            nameFilter = nameProperty + "." + nameFilter;
                            keyValue = nameFilter.replaceAll("[.]", "_");
                        }
                        if (isString(typeJoin)) {
                            if (matchMode == MatchMode.END) {
                                propertyValues.put(keyValue, value + "%");
                            } else if (matchMode == MatchMode.START) {
                                propertyValues.put(keyValue, "%" + value);
                            } else if (matchMode == MatchMode.ANYWHERE) {
                                propertyValues.put(keyValue, "%" + value + "%");
                            } else if (matchMode == MatchMode.EXACT) {
                                propertyValues.put(keyValue, value);
                            }
                        } else if (typeJoin.isAnnotationPresent(Entity.class)) {
                            propertyValues.putAll(getFilterValues(value, nameFilter));
                        } else if (!List.class.isAssignableFrom(typeJoin)) {
                            propertyValues.put(keyValue, value);
                        }
                        if (isPrimaryKeyProperty(objeto, field)) {
                            break;
                        }
                    }
                } catch (Exception e) {
                    logger.error(e);
                }
            }
        }
        return propertyValues;
    }

    public Map<String, Object> getPropertyValues() {
        Map<String, Object> propertyValues = new HashMap<String, Object>();
        if (entity != null) {
            String name = entity.getClass().getSimpleName();
            propertyValues = getFilterValues(entity, name.toLowerCase());
        }
        return propertyValues;
    }
}
